/*
 * Copyright 2008 Slava Chernyak
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package com.chernyak.DataAdapter.types;

import com.chernyak.DataAdapter.CompilerException;

public class ArrayedTypeDecl extends TypeDecl {
	protected TypeDecl arrayedType;
	protected int arrayDims;
	protected int arrayFixedSize = -1; 
	
	public ArrayedTypeDecl(TypeDecl arrayedType, String name, int arrayDims, int ln) {
		super(name, false, ln);
		this.arrayedType = arrayedType;
		this.arrayDims = arrayDims;
	}
	
	public ArrayedTypeDecl(
	    TypeDecl arrayedType, String name, int arrayDims, int arrayFixedSize, int ln) {
		super(name, false, ln);
		this.arrayedType = arrayedType;
		this.arrayDims = arrayDims;
		this.arrayFixedSize = arrayFixedSize;
	}
	
	public boolean isFixedSize() {
		return (arrayFixedSize != -1);
	}
	
	public int getArrayFixedSize() {
		return this.arrayFixedSize;
	}
	
	public int getArrayDims() {
		return arrayDims;
	}
	
	public TypeDecl getArrayedType() {
		return arrayedType;
	}

	@Override
	protected void lowerRead(StringBuilder sink, int arrayDim) {
		/* no op */
	}

	@Override
	protected void lowerWrite(StringBuilder sink, int arrayDim) {
		/* no op */
	}
	
	@Override
	protected void lowerToString(StringBuilder sink, int arrayDim) {
		/* no op */
	}

	@Override
	public void lowerDeclarations(StringBuilder sink) {
		sink.append("public " + arrayedType.getTypeName() + " " + name + 
		    genArrayBrackets(arrayDims) + " = null;" );
	}

	@Override
	public void lowerEquals(StringBuilder sink) {
		//TODO: implement equality for n-dimensional arrays 
	  if (arrayedType instanceof PrimitiveTypeDecl) {
	    sink.append("__da_test &= Arrays.equals(this." + name + ", __da_o." + name + ");");
	  } else {
	    sink.append("__da_test &= Arrays.deepEquals(this." + name + ", __da_o." + name + ");");
	  }
	}
	
	public void lowerHashCode(StringBuilder sink) {
		if (compare) {
			sink.append("if (null == " + name + ") return -1;");
			sink.append("return " + name + ".hashCode();");
		}
	}
	
	public void lowerToString(StringBuilder sink) {
		//TODO: factor null-checking into dimension for-loop
		sink.append("// " + name + " ARRAY TO STRING ;");
		sink.append("// " + name + " ARRAY WRITE ;");
		sink.append("if ( " + name + " == null ) {");
		sink.append(		"__da_str += \"null\" + __DA_STRING_DELIM;");
		sink.append("} else {");
		for (int i = 0; i < arrayDims; i++) {
			sink.append("for (int __da_ad_" + i + " = 0; __da_ad_" + i + " < "
			    + name + genArrayDeref(i) + ".length ; __da_ad_" + i + "++ ) {");
		}
		arrayedType.lowerToString(sink, arrayDims);
		for (int i = 0; i < arrayDims; i++) {
			sink.append("}");
		}
		sink.append("}");
	}

	public void lowerComparisonWCG(StringBuilder sink) {
		/* no op */
	}
	
	private String genArrayBrackets(int dim) {
		String brackets = "";
		for (int i = 0; i < dim; i++ ) {
			brackets += "[]";
		}
		return brackets;
	}

	public void lowerReadWCG(StringBuilder sink) {
		//TODO: factor null-checking into dimension for-loop
		sink.append("// " + name + " ARRAY READ ;");
		sink.append("{");
		sink.append("	if (d.readBoolean() == false) {");
		sink.append(		name + " = null;");
		sink.append("	} else {");	
		for (int i = 0; i < arrayDims; i++) {
			sink.append("int __da_length_" + i + " = d.readInt();");
			sink.append(name + genArrayDeref(i) + " = new " + arrayedType.getTypeName() + 
			    "[__da_length_" + i + "]" + genArrayBrackets(arrayDims - i - 1) + ";");
			sink.append("for (int __da_ad_" + i + " = 0; __da_ad_" + i + " < __da_length_" + 
			    i + " ; __da_ad_" + i + "++ ) {");
		}
		arrayedType.lowerRead(sink, arrayDims);
		for (int i = 0; i < arrayDims; i++) {
			sink.append("}");
		}
		sink.append("	}");
		sink.append("}");
	}
	
	public void lowerParse(StringBuilder sink) {
		if (isFixedSize()) {
			// TODO: implement fixed-size array parse case
			
		} else {
			if (arrayDims != 1 ||
			    ! ( arrayedType instanceof PrimitiveTypeDecl ||
			        arrayedType instanceof StringDecl )) {
				throw new CompilerException("Cannot parse array '" + name + "'", ln);
			}
			
			String boxedTypeName = null;
			if (arrayedType instanceof PrimitiveTypeDecl) {
				boxedTypeName = ((PrimitiveTypeDecl)arrayedType).getBoxedTypeName();
			} else {
				boxedTypeName = ((StringDecl)arrayedType).getBoxedTypeName();
			}
			
			sink.append("{");
			sink.append("  ArrayList<" + boxedTypeName + "> __da_al = new ArrayList<" + 
			    boxedTypeName + ">();");
			sink.append("  while(__da_tokens.hasMoreTokens()) {");
			sink.append("    " + boxedTypeName + " __da_temp = ");
			if (arrayedType instanceof PrimitiveTypeDecl) {
				((PrimitiveTypeDecl)arrayedType).lowerParse(sink,1);
			} else {
				((StringDecl)arrayedType).lowerParse(sink,1);
			}
			sink.append("    __da_al.add(__da_temp);");
			sink.append("  }");
			sink.append("  __da_o." + name + " = new " + arrayedType.getTypeName() + 
			    "[__da_al.size()];");
			sink.append("  int i = 0;");
			sink.append("  for(" + boxedTypeName + " item : __da_al) {");
			sink.append("     __da_o." + name + "[i++] = item;");
			sink.append("  }");
			sink.append("}");
		}
	}

	public void lowerWriteWCG(StringBuilder sink) {
		//TODO: factor null-checking into dimension for-loop
		sink.append("// " + name + " ARRAY WRITE ;");
		sink.append("if ( " + name + " == null ) {");
		sink.append(	"d.writeBoolean(false);");
		sink.append("} else {");
		sink.append(	"d.writeBoolean(true);\n");
		for (int i = 0; i < arrayDims; i++) {
			sink.append("d.writeInt(" + name + genArrayDeref(i) + ".length);");
			sink.append("for (int __da_ad_" + i + " = 0; __da_ad_" + i + " < " + name + 
			    genArrayDeref(i) + ".length ; __da_ad_" + i + "++ ) {");
		}
		arrayedType.lowerWrite(sink, arrayDims);
		for (int i = 0; i < arrayDims; i++) {
			sink.append("}");
		}
		sink.append("}");
	}
	
	public String getTypeName() {
		return this.arrayedType.getTypeName() + this.genArrayBrackets(arrayDims);
	}

}
